Skip to content

feat: add HTTP proxy support via environment variables#622

Merged
jpoehnelt merged 4 commits intogoogleworkspace:mainfrom
femto:feat/proxy-support-rebased
Mar 25, 2026
Merged

feat: add HTTP proxy support via environment variables#622
jpoehnelt merged 4 commits intogoogleworkspace:mainfrom
femto:feat/proxy-support-rebased

Conversation

@femto
Copy link
Contributor

@femto femto commented Mar 25, 2026

Summary

When http_proxy/https_proxy/all_proxy environment variables are set, use reqwest (which supports proxy env vars natively) for token refresh and the auth login token exchange instead of relying only on yup-oauth2's hyper-based client.

This enables gws to work in environments that require an HTTP proxy to access Google APIs.

Supersedes closed PR #423 after rebasing onto the cargo workspace refactor.

Changes

  • crates/google-workspace-cli/src/auth.rs
    • Add shared proxy env detection
    • Add proxy-aware token refresh via reqwest
    • Improve error-body handling for failed token refresh responses
  • crates/google-workspace-cli/src/auth_commands.rs
    • Add proxy-aware OAuth login flow for auth code exchange
    • Extract the non-proxy yup-oauth flow into a helper for readability
    • Reuse shared proxy/error helpers and keep the newer workspace auth scope behavior intact
  • crates/google-workspace-cli/Cargo.toml
    • Keep reqwest configured with SOCKS support in the workspace crate
  • .changeset/proxy-support-review-fixes.md
    • Include a patch changeset for the CLI package

Testing

  • cargo clippy -p google-workspace-cli -- -D warnings
  • cargo test -p google-workspace-cli auth::tests::
  • cargo test -p google-workspace-cli auth_commands::tests::

femto added 3 commits March 25, 2026 12:10
When http_proxy/https_proxy/all_proxy environment variables are set,
use reqwest (which natively supports proxy) for token refresh instead
of yup-oauth2's hyper-based client (which doesn't support proxy).

This enables gws to work in environments that require HTTP proxy to
access Google APIs (e.g., users in China).

Changes:
- Cargo.toml: Enable reqwest's default features including proxy support
- src/auth.rs: Add proxy-aware token refresh using reqwest as fallback

Fixes googleworkspace#422
When proxy env vars are set, use a custom OAuth flow with reqwest
for token exchange instead of yup-oauth2's hyper-based client.

Changes to auth_commands.rs:
- Add login_with_proxy_support() for proxy-aware OAuth login
- Add exchange_code_with_reqwest() for token exchange via reqwest
- Detect proxy env vars and choose appropriate flow
@changeset-bot
Copy link

changeset-bot bot commented Mar 25, 2026

🦋 Changeset detected

Latest commit: ec20bdf

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@googleworkspace/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the gws command-line interface by integrating robust HTTP proxy support. It allows the CLI to seamlessly authenticate and interact with Google APIs in network environments that mandate the use of an HTTP proxy. The changes involve a strategic use of the reqwest library for proxy-aware token management and OAuth flows, ensuring that the tool remains functional and reliable across diverse network configurations.

Highlights

  • HTTP Proxy Support: Implemented support for http_proxy, https_proxy, and all_proxy environment variables, enabling the gws CLI to function in proxy-restricted environments.
  • Conditional Client Usage: The CLI now dynamically switches between reqwest (which natively supports proxy environment variables) and yup-oauth2 for token operations. reqwest is used when proxy environment variables are detected, otherwise yup-oauth2 is used for its speed and caching.
  • Proxy-Aware Token Refresh: The access token refresh mechanism was updated to use reqwest when a proxy is configured, ensuring successful token acquisition through the proxy.
  • Proxy-Aware OAuth Login Flow: The OAuth login process for exchanging authorization codes for tokens has been refactored to use reqwest when proxy environment variables are present, facilitating authentication in proxy environments.
  • Improved Error Handling: Enhanced error reporting for failed token refresh responses by including the response body, providing more diagnostic information.
  • SOCKS Proxy Feature: The reqwest dependency was updated to include the socks feature, adding support for SOCKS proxies.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces proxy support for OAuth authentication flows in the Google Workspace CLI. This is achieved by adding the socks feature to the reqwest dependency and implementing new functions that leverage reqwest for proxy-aware token refreshing and authorization code exchange. The existing authentication logic has been updated to conditionally use these new proxy-aware flows when proxy environment variables are detected. Unit tests for the new proxy-related utilities have also been added. The review comments suggest that creating a new reqwest::Client for each function call is inefficient and recommend using a single, shared client instance across the application to improve performance.

@femto
Copy link
Contributor Author

femto commented Mar 25, 2026

Addressed the reqwest client reuse feedback by adding a shared client helper backed by std::sync::OnceLock in crates/google-workspace/src/client.rs and switching the proxy auth flows to use it.

@jpoehnelt jpoehnelt enabled auto-merge (squash) March 25, 2026 15:44
@jpoehnelt jpoehnelt merged commit a52d297 into googleworkspace:main Mar 25, 2026
10 checks passed
@googleworkspace-bot
Copy link
Collaborator

/gemini review

@codecov
Copy link

codecov bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 35.65460% with 231 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.88%. Comparing base (9198386) to head (ec20bdf).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
crates/google-workspace-cli/src/auth_commands.rs 29.47% 189 Missing ⚠️
crates/google-workspace-cli/src/auth.rs 43.47% 39 Missing ⚠️
crates/google-workspace/src/client.rs 86.36% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #622      +/-   ##
==========================================
- Coverage   71.09%   70.88%   -0.21%     
==========================================
  Files          44       44              
  Lines       20403    20642     +239     
==========================================
+ Hits        14506    14633     +127     
- Misses       5897     6009     +112     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request significantly improves proxy support for OAuth authentication flows within the CLI. It introduces conditional logic to use reqwest for token exchange and refresh when proxy environment variables are detected, addressing limitations with the yup-oauth2 library's hyper-based client. Key changes include adding the socks feature to reqwest, refactoring OAuth login and token refresh into dedicated proxy-aware functions, and introducing a shared reqwest::Client instance for efficient HTTP requests. A review comment highlighted an improvement opportunity regarding the robustness of temporary file cleanup during the OAuth flow, suggesting the use of an RAII guard to prevent sensitive data leaks in case of panics.

Comment on lines +196 to +230
let temp_path = config_dir.join("credentials.tmp");
let _ = std::fs::remove_file(&temp_path);

let result = async {
let auth = yup_oauth2::InstalledFlowAuthenticator::builder(
secret,
yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect,
)
.with_storage(Box::new(crate::token_storage::EncryptedTokenStorage::new(
temp_path.clone(),
)))
.force_account_selection(true)
.flow_delegate(Box::new(CliFlowDelegate { login_hint: None }))
.build()
.await
.map_err(|e| GwsError::Auth(format!("Failed to build authenticator: {e}")))?;

let scope_refs: Vec<&str> = scopes.iter().map(|s| s.as_str()).collect();
let token = auth
.token(&scope_refs)
.await
.map_err(|e| GwsError::Auth(format!("OAuth flow failed: {e}")))?;

let access_token = token
.token()
.ok_or_else(|| GwsError::Auth("No access token returned".to_string()))?
.to_string();
let refresh_token = read_refresh_token_from_cache(&temp_path)?;

Ok((access_token, refresh_token))
}
.await;

let _ = std::fs::remove_file(&temp_path);
result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The manual cleanup of the temporary file credentials.tmp is not robust against panics. If a panic occurs during the OAuth flow, the temporary file containing sensitive token data (though encrypted) might be left on disk.

To ensure the file is always cleaned up, you can use an RAII guard. This also simplifies the code by removing the explicit cleanup at the end and the unnecessary async block.

    let temp_path = config_dir.join("credentials.tmp");
    // Clean up any stale file from a previous run.
    let _ = std::fs::remove_file(&temp_path);

    // RAII guard to ensure the temporary file is removed on scope exit, even on panics.
    struct TempFileGuard<'a>(&'a Path);
    impl<'a> Drop for TempFileGuard<'a> {
        fn drop(&mut self) {
            let _ = std::fs::remove_file(self.0);
        }
    }
    let _guard = TempFileGuard(&temp_path);

    let auth = yup_oauth2::InstalledFlowAuthenticator::builder(
        secret,
        yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect,
    )
    .with_storage(Box::new(crate::token_storage::EncryptedTokenStorage::new(
        temp_path.clone(),
    )))
    .force_account_selection(true)
    .flow_delegate(Box::new(CliFlowDelegate { login_hint: None }))
    .build()
    .await
    .map_err(|e| GwsError::Auth(format!("Failed to build authenticator: {e}")))?;

    let scope_refs: Vec<&str> = scopes.iter().map(|s| s.as_str()).collect();
    let token = auth
        .token(&scope_refs)
        .await
        .map_err(|e| GwsError::Auth(format!("OAuth flow failed: {e}")))?;

    let access_token = token
        .token()
        .ok_or_else(|| GwsError::Auth("No access token returned".to_string()))?
        .to_string();
    let refresh_token = read_refresh_token_from_cache(&temp_path)?;

    Ok((access_token, refresh_token))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants